package pikes.ecma;

import java.io.IOException;

import pikes.core.Printable;
import pikes.core.PrintableCharSequence;
import pikes.core.Printer;
import pikes.core.PrinterAsAppendableAdapter;

public class FormattingScriptPrinter implements SyntaxTreeVisitor {

	private Printer printer = null;

	private boolean indent = false;

	private long tagLevel = 0;

	private boolean newLine = false;

	private boolean spaceBeforeOpeningBrace = false;

	private boolean startBlockInNewLine = false;

	private boolean spaceBeforeAssignmentOperator = false;

	private boolean spaceAfterAssignmentOperator = false;

	private boolean spaceBeforeBinaryOperator = false;

	private boolean spaceAfterBinaryOperator = false;

	private boolean newLineAfterEndOfStatement = false;

	public FormattingScriptPrinter(final Appendable appendable) {
		this(appendable, false);
	}

	public FormattingScriptPrinter(final Printer printer) {
		this(printer, false);
	}

	public FormattingScriptPrinter(final Appendable appendable, final boolean newLine) {
		this(new Printer(appendable), newLine);
	}

	public FormattingScriptPrinter(final Printer printer, final boolean newLine) {
		this.printer = printer;
		this.newLine = newLine;
	}

	public final void visitSourceElement(final SourceElement sourceElement) throws EcmaSyntaxVisitingException {
		try {
			sourceElement.print(new PrinterAsAppendableAdapter(printer));
			if (newLine) {
				newLine();
			}
		}
		catch (IOException e) {
			throw new EcmaSyntaxVisitingException(e);
		}
	}

	private void space() throws EcmaSyntaxVisitingException {
		print(' ');
	}

	private void newLine() throws EcmaSyntaxVisitingException {
		printer.newLine();
	}

	private void indent() throws EcmaSyntaxVisitingException {
		for (int i = 0; i < tagLevel; i++) {
			printer.tabulator();
		}
	}

	private void print(final Printable printable) throws EcmaSyntaxVisitingException {

		try {
			printable.print(new PrinterAsAppendableAdapter(printer));
		}
		catch (IOException e) {
			throw new EcmaSyntaxVisitingException(e);
		}
	}

	private void print(final char c) throws EcmaSyntaxVisitingException {
		printer.print(c);
	}

	private void print(final CharSequence charSequence) throws EcmaSyntaxVisitingException {
		printer.print(charSequence);
	}

	public final void beforeSourceElement() throws EcmaSyntaxVisitingException {
		if (isIndent()) {
			indent();
		}
	}

	public final void beforeParameterList() throws EcmaSyntaxVisitingException {
		print('(');
	}

	public final void afterParameterList() throws EcmaSyntaxVisitingException {
		print(')');
	}

	public final void closeCurlyBrace() throws EcmaSyntaxVisitingException {
		tagLevel--;
		print('}');
	}

	public final void openCurlyBrace() throws EcmaSyntaxVisitingException {
		if (isSpaceBeforeOpeningBrace()) {
			space();
		}
		print('{');
		tagLevel++;
		if (isStartBlockInNewLine()) {
			newLine();
		}
	}

	public final boolean isSpaceBeforeOpeningBrace() {
		return spaceBeforeOpeningBrace;
	}

	public final void setSpaceBeforeOpeningBrace(final boolean spaceBeforeOpeningBrace) {
		this.spaceBeforeOpeningBrace = spaceBeforeOpeningBrace;
	}

	public final boolean isStartBlockInNewLine() {
		return startBlockInNewLine;
	}

	public final void setStartBlockInNewLine(boolean startBlockInNewLine) {
		this.startBlockInNewLine = startBlockInNewLine;
	}

	public final void beforeVariableDeclarations() throws EcmaSyntaxVisitingException {
		print("var");
		space();
	}

	public final void identifier(CharSequence text) {
		print(new PrintableCharSequence(text));
	}

	public final void identifier(Printable text) {
		print(text);
	}

	public final void equalSign() throws EcmaSyntaxVisitingException {
		if (isSpaceBeforeAssignmentOperator()) {
			space();
		}
		print('=');
		if (isSpaceAfterAssignmentOperator()) {
			space();
		}
	}

	public final void comma() throws EcmaSyntaxVisitingException {
		print(',');
	}

	public final void endOfStatement() throws EcmaSyntaxVisitingException {
		print(';');
		if (isNewLineAfterEndOfStatement()) {
			newLine();
		}
		/*
		 * if (isIndent()) { indent(); }
		 */
	}

	public final boolean isSpaceBeforeAssignmentOperator() {
		return spaceBeforeAssignmentOperator;
	}

	public final void setSpaceBeforeAssignmentOperator(boolean spaceBeforeAssignmentOperator) {
		this.spaceBeforeAssignmentOperator = spaceBeforeAssignmentOperator;
	}

	public final boolean isSpaceAfterAssignmentOperator() {
		return spaceAfterAssignmentOperator;
	}

	public final void setSpaceAfterAssignmentOperator(boolean spaceAfterAssignmentOperator) {
		this.spaceAfterAssignmentOperator = spaceAfterAssignmentOperator;
	}

	public final void binaryOperator(CharSequence operator) throws EcmaSyntaxVisitingException {
		if (isSpaceBeforeBinaryOperator()) {
			space();
		}
		print(operator);
		if (isSpaceAfterBinaryOperator()) {
			space();
		}
	}

	public final boolean isSpaceBeforeBinaryOperator() {
		return spaceBeforeBinaryOperator;
	}

	public final void setSpaceBeforeBinaryOperator(boolean spaceBeforeBinaryOperator) {
		this.spaceBeforeBinaryOperator = spaceBeforeBinaryOperator;
	}

	public final boolean isSpaceAfterBinaryOperator() {
		return spaceAfterBinaryOperator;
	}

	public final void setSpaceAfterBinaryOperator(boolean spaceAfterBinaryOperator) {
		this.spaceAfterBinaryOperator = spaceAfterBinaryOperator;
	}

	public final void dotOperator() throws EcmaSyntaxVisitingException {
		print('.');
	}

	public final void New() throws EcmaSyntaxVisitingException {
		print("new");
	}

	public final void Return() throws EcmaSyntaxVisitingException {
		print("return");
	}

	public final void Function() throws EcmaSyntaxVisitingException {
		print("function");
	}

	public final void Do() throws EcmaSyntaxVisitingException {
		print("do");
	}

	public final void While() throws EcmaSyntaxVisitingException {
		print("while");
	}

	public final void Else() throws EcmaSyntaxVisitingException {
		print("else");
	}

	public final void If() throws EcmaSyntaxVisitingException {
		print("if");
	}

	public final void colon() throws EcmaSyntaxVisitingException {
		print(':');
	}

	public final void questionMark() throws EcmaSyntaxVisitingException {
		print('?');
	}

	public final boolean isNewLineAfterEndOfStatement() {
		return newLineAfterEndOfStatement;
	}

	public final void setNewLineAfterEndOfStatement(boolean newLineAfterEndOfStatement) {
		this.newLineAfterEndOfStatement = newLineAfterEndOfStatement;
	}

	public final void whiteSpace() throws EcmaSyntaxVisitingException {
		space();
	}

	public final void stringQuote() throws EcmaSyntaxVisitingException {
		print('\'');
	}

	public final void closeSquaredBrace() throws EcmaSyntaxVisitingException {
		print(']');
	}

	public final void openSquaredBrace() throws EcmaSyntaxVisitingException {
		print('[');
	}

	public final void setIndent(boolean b) {
		this.indent = b;
	}

	public final boolean isIndent() {
		return indent;
	}
}
